<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>文件上传下载服务</title>
    <style>
      /* 页面主体样式 */
      body {
        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        max-width: 800px;
        margin: 0 auto;
        padding: 20px;
        line-height: 1.6;
      }
      /* 标题样式 */
      h1 {
        color: #333;
        text-align: center;
        margin-bottom: 30px;
      }
      /* 卡片容器样式 */
      .card {
        background-color: #f8f9fa;
        border-radius: 8px;
        padding: 20px;
        margin-bottom: 20px;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
      }
      /* 文件表单样式 */
      .file-form {
        display: flex;
        flex-direction: column;
        gap: 10px;
      }
      /* 按钮基本样式 */
      .btn {
        background-color: #4caf50;
        color: white;
        padding: 10px 15px;
        border: none;
        border-radius: 4px;
        cursor: pointer;
        font-size: 16px;
        transition: background-color 0.3s;
      }
      /* 按钮悬停效果 */
      .btn:hover {
        background-color: #45a049;
      }
      /* 次要按钮样式 */
      .btn-secondary {
        background-color: #6c757d;
      }
      /* 次要按钮悬停效果 */
      .btn-secondary:hover {
        background-color: #5a6268;
      }
      /* 进度条样式 */
      progress {
        width: 100%;
        height: 20px;
        margin-top: 10px;
      }
      /* 文件列表样式 */
      .file-list {
        list-style: none;
        padding: 0;
      }
      /* 文件项样式 */
      .file-item {
        display: flex;
        flex-direction: column;
        padding: 10px;
        border-bottom: 1px solid #ddd;
      }
      /* 文件项下载状态区域 */
      .download-status {
        margin-top: 5px;
        font-size: 0.9em;
        color: #666;
      }
      .file-item-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        width: 100%;
      }
      /* 最后一个文件项无底部边框 */
      .file-item:last-child {
        border-bottom: none;
      }
      /* 文件信息容器 */
      .file-info {
        display: flex;
        flex-direction: column;
      }
      /* 文件名样式 */
      .file-name {
        font-weight: bold;
      }
      /* 文件大小样式 */
      .file-size {
        color: #6c757d;
        font-size: 0.9em;
      }
      /* 文件操作按钮容器 */
      .file-actions {
        display: flex;
        gap: 10px;
      }
      /* 提示信息样式 */
      .info-text {
        font-size: 0.9em;
        color: #666;
        margin-top: 5px;
      }
      /* 分片下载进度条容器 */
      .chunk-progress-container {
        margin-top: 5px;
        display: none;
      }
      /* 分片进度条 */
      .chunk-progress {
        width: 100%;
        height: 20px;
        margin-bottom: 5px;
        position: relative;
      }
      /* 分片进度显示 */
      .chunk-label {
        position: absolute;
        left: 50%;
        transform: translateX(-50%);
        color: #333;
        font-size: 12px;
        line-height: 20px;
      }
      /* 下载速度显示 */
      .download-speed {
        font-size: 0.8em;
        color: #666;
        margin-top: 5px;
      }
      /* 暂停/继续按钮 */
      .pause-resume-btn {
        background-color: #ffc107;
      }
      .pause-resume-btn:hover {
        background-color: #e0a800;
      }
      /* 下载耗时显示 */
      .download-time {
        font-size: 0.8em;
        color: #666;
        margin-top: 5px;
      }
      /* 下载统计面板 */
      .download-stats {
        margin-top: 10px;
        padding: 10px;
        background-color: #f0f0f0;
        border-radius: 4px;
        display: none;
      }
    </style>
    <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
  </head>
  <body>
    <div id="root"></div>

    <script type="text/babel">
      // 常量定义
      const CHUNK_SIZE = 1024 * 1024 * 2;
      const MAX_CONCURRENT_CHUNKS = 3;

      // 工具函数
      const formatFileSize = (bytes) => {
        if (bytes === 0) return '0 Bytes';
        const k = 1024;
        const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
        const i = Math.floor(Math.log(bytes) / Math.log(k));
        return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
      };

      const formatTime = (milliseconds) => {
        if (milliseconds < 1000) return `${milliseconds}毫秒`;
        const seconds = Math.floor(milliseconds / 1000);
        const minutes = Math.floor(seconds / 60);
        const remainingSeconds = seconds % 60;
        if (minutes > 0) return `${minutes}分${remainingSeconds}秒`;
        return `${seconds}秒`;
      };

      // 文件上传组件
      const FileUpload = ({ onFileUploaded }) => {
        const [progress, setProgress] = React.useState(0);
        const [status, setStatus] = React.useState('');
        const [showProgress, setShowProgress] = React.useState(false);
        const fileInputRef = React.useRef(null);

        const handleSubmit = async (e) => {
          e.preventDefault();
          const file = fileInputRef.current.files[0];

          if (!file) {
            setStatus('请选择文件');
            return;
          }

          if (file.size > 1024 * 1024 * 1024) {
            setStatus('文件过大，最大支持1GB');
            return;
          }

          const formData = new FormData();
          formData.append('file', file);
          setShowProgress(true);
          setProgress(0);
          setStatus('开始上传...');

          try {
            const xhr = new XMLHttpRequest();
            xhr.timeout = 60000;

            xhr.upload.onprogress = (e) => {
              if (e.lengthComputable) {
                const percent = (e.loaded / e.total) * 100;
                setProgress(percent);
                setStatus(`上传中: ${Math.round(percent)}%`);
              }
            };

            xhr.onload = () => {
              if (xhr.status === 200 || xhr.status === 201) {
                setStatus('上传成功！');
                fileInputRef.current.value = '';
                onFileUploaded();
              } else {
                setStatus(`上传失败: ${xhr.statusText || '服务器错误'}`);
              }
              setShowProgress(false);
            };

            xhr.onerror = () => {
              setStatus('上传失败: 网络错误');
              setShowProgress(false);
            };

            xhr.ontimeout = () => {
              setStatus('上传超时，请重试');
              setShowProgress(false);
            };

            xhr.open('POST', '/files/upload', true);
            xhr.send(formData);
          } catch (error) {
            setStatus(`上传失败: ${error.message}`);
            setShowProgress(false);
          }
        };

        return (
          <div className="card">
            <h2>上传文件</h2>
            <form className="file-form" onSubmit={handleSubmit}>
              <input type="file" ref={fileInputRef} name="file" required />
              <button type="submit" className="btn">
                上传文件
              </button>
              {showProgress && <progress value={progress} max="100" />}
              {status && <div>{status}</div>}
              <p className="info-text">✅ 支持上传最大1GB的文件</p>
            </form>
          </div>
        );
      };

      // 分片下载管理器
      class ChunkDownloadManager {
        constructor(filename, fileSize, onProgressUpdate, onStatusUpdate) {
          this.filename = filename;
          this.fileSize = fileSize;
          this.chunks = [];
          this.downloaded = new Set();
          this.retries = new Map();
          this.maxRetries = 3;
          this.paused = false;
          this.aborted = false;
          this.startTime = Date.now();
          this.downloadedSize = 0;
          this.chunkStreams = new Map();
          this.onProgressUpdate = onProgressUpdate;
          this.onStatusUpdate = onStatusUpdate;
        }

        async start() {
          const chunkSize = Math.min(CHUNK_SIZE, Math.ceil(this.fileSize / 10));
          for (let start = 0; start < this.fileSize; start += chunkSize) {
            const end = Math.min(start + chunkSize - 1, this.fileSize - 1);
            this.chunks.push({ start, end });
          }

          await this.downloadChunks();
        }

        async downloadChunks() {
          try {
            const concurrentChunks = 3;
            let activeDownloads = 0;
            let nextChunkIndex = 0;

            while (nextChunkIndex < this.chunks.length && !this.aborted) {
              if (this.paused) {
                await new Promise((resolve) => setTimeout(resolve, 1000));
                continue;
              }

              while (activeDownloads < concurrentChunks && nextChunkIndex < this.chunks.length) {
                const index = nextChunkIndex++;
                if (!this.downloaded.has(index)) {
                  activeDownloads++;
                  this.downloadChunk(index, this.chunks[index]).finally(() => {
                    activeDownloads--;
                  });
                }
              }

              await new Promise((resolve) => setTimeout(resolve, 100));
            }

            if (!this.aborted && this.downloaded.size === this.chunks.length) {
              await this.mergeAndDownload();
            }
          } catch (error) {
            console.error('Download failed:', error);
            this.onStatusUpdate('下载失败，请重试');
          }
        }

        async downloadChunk(index, { start, end }) {
          if (this.aborted) return;

          const retryCount = this.retries.get(index) || 0;
          if (retryCount >= this.maxRetries) {
            throw new Error(`分片 ${index + 1} 下载失败次数过多`);
          }

          try {
            const response = await fetch(`/files/chunk-download/${this.filename}`, {
              headers: { Range: `bytes=${start}-${end}` },
            });

            if (!response.ok) throw new Error('Chunk download failed');

            const reader = response.body.getReader();
            const contentLength = parseInt(response.headers.get('Content-Length'), 10);
            let receivedLength = 0;
            const chunks = [];

            while (true) {
              const { done, value } = await reader.read();
              if (done) break;

              chunks.push(value);
              receivedLength += value.length;
              this.downloadedSize += value.length;

              const progress = (receivedLength / contentLength) * 100;
              this.onProgressUpdate(index, progress);
            }

            const blob = new Blob(chunks);
            this.chunks[index] = blob;
            this.downloaded.add(index);
            this.onProgressUpdate(index, 100);
          } catch (error) {
            console.error(`分片 ${index + 1} 下载失败:`, error);
            this.retries.set(index, retryCount + 1);
            if (retryCount < this.maxRetries) {
              await new Promise((resolve) => setTimeout(resolve, 1000 * (retryCount + 1)));
              return this.downloadChunk(index, { start, end });
            }
            throw error;
          }
        }

        async mergeAndDownload() {
          try {
            const blobs = this.chunks.filter((chunk) => chunk instanceof Blob);
            const finalBlob = new Blob(blobs);

            const url = URL.createObjectURL(finalBlob);
            const a = document.createElement('a');
            a.href = url;
            a.download = this.filename;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);

            const endTime = Date.now();
            const duration = endTime - this.startTime;
            this.onStatusUpdate(`下载完成 | 总耗时: ${formatTime(duration)}`);

            setTimeout(() => {
              URL.revokeObjectURL(url);
              this.cleanup();
            }, 1000);
          } catch (error) {
            console.error('Error merging file:', error);
            this.onStatusUpdate('合并文件失败，请重试');
          }
        }

        cleanup() {
          this.chunks.forEach((chunk, index) => {
            if (this.downloaded.has(index)) {
              URL.revokeObjectURL(this.chunks[index]);
              this.chunks[index] = null;
            }
          });
          this.chunks = [];
          this.downloaded.clear();
          this.chunkStreams.clear();
        }

        togglePause() {
          this.paused = !this.paused;
          return this.paused;
        }

        abort() {
          this.aborted = true;
          this.paused = true;
          this.chunkStreams.forEach((stream) => stream.cancel());
          this.chunkStreams.clear();
          this.cleanup();
        }
      }

      // 文件项组件
      const FileItem = ({ file, onFileDownloaded }) => {
        const [downloadStatus, setDownloadStatus] = React.useState('');
        const [chunkProgress, setChunkProgress] = React.useState([]);
        const [isPaused, setIsPaused] = React.useState(false);
        const downloadManagerRef = React.useRef(null);

        const handleRegularDownload = async () => {
          const startTime = Date.now();
          setDownloadStatus('开始下载...');

          try {
            const response = await fetch(file.url, { method: 'HEAD' });
            const contentLength = response.headers.get('content-length');

            const a = document.createElement('a');
            a.href = file.url;
            a.style.display = 'none';
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);

            const estimatedTime = Math.ceil(contentLength / (1024 * 1024));
            setTimeout(() => {
              const duration = Date.now() - startTime;
              setDownloadStatus(`下载完成 | 总耗时: ${formatTime(duration)}`);
            }, estimatedTime * 1000);
          } catch (error) {
            console.error('下载失败:', error);
            setDownloadStatus('下载失败，请重试');
          }
        };

        const handleStreamDownload = () => {
          const startTime = Date.now();
          setDownloadStatus('开始加载...');
          window.open(`/files/stream/${file.filename}`, '_blank');

          setTimeout(() => {
            const duration = Date.now() - startTime;
            setDownloadStatus(`加载完成 | 总耗时: ${formatTime(duration)}`);
          }, 1000);
        };

        const handleFastDownload = () => {
          if (downloadManagerRef.current) {
            alert('该文件已在下载中');
            return;
          }

          const progressUpdater = (index, progress) => {
            setChunkProgress((prev) => {
              const newProgress = [...prev];
              newProgress[index] = progress;
              return newProgress;
            });
          };

          const statusUpdater = (status) => {
            setDownloadStatus(status);
          };

          downloadManagerRef.current = new ChunkDownloadManager(
            file.filename,
            file.size,
            progressUpdater,
            statusUpdater,
          );
          downloadManagerRef.current.start();
        };

        const togglePause = () => {
          if (downloadManagerRef.current) {
            const paused = downloadManagerRef.current.togglePause();
            setIsPaused(paused);
          }
        };

        return (
          <li className="file-item">
            <div className="file-item-header">
              <div className="file-info">
                <span className="file-name">{file.filename}</span>
                <span className="file-size">{formatFileSize(file.size)}</span>
              </div>
              <div className="file-actions">
                <button className="btn" onClick={handleRegularDownload}>
                  普通下载
                </button>
                <button className="btn" onClick={handleStreamDownload}>
                  在线预览
                </button>
                <button className="btn" onClick={handleFastDownload}>
                  快速下载
                </button>
                {chunkProgress.length > 0 && (
                  <button className="btn pause-resume-btn" onClick={togglePause}>
                    {isPaused ? '继续' : '暂停'}
                  </button>
                )}
              </div>
            </div>
            <div className="download-status">{downloadStatus}</div>
            {chunkProgress.length > 0 && (
              <div className="chunk-progress-container">
                {chunkProgress.map((progress, index) => (
                  <div key={index} className="chunk-progress">
                    <progress value={progress} max="100" />
                    <span className="chunk-label">
                      分片 {index + 1}: {progress.toFixed(1)}%
                    </span>
                  </div>
                ))}
              </div>
            )}
          </li>
        );
      };

      // 文件列表组件
      const FileList = () => {
        const [files, setFiles] = React.useState([]);
        const [status, setStatus] = React.useState('加载文件列表...');

        const fetchFileList = async () => {
          try {
            const response = await fetch('/files/list');
            if (!response.ok) throw new Error('获取文件列表失败');
            const data = await response.json();
            setFiles(data);
            setStatus(data.length ? '' : '没有可用文件');
          } catch (error) {
            setStatus(`错误: ${error.message}`);
          }
        };

        React.useEffect(() => {
          fetchFileList();
        }, []);

        return (
          <div className="card">
            <h2>可用文件</h2>
            <button className="btn btn-secondary" onClick={fetchFileList}>
              刷新列表
            </button>
            <div id="fileListContainer">
              {status ? (
                <div>{status}</div>
              ) : (
                <ul className="file-list">
                  {files.map((file) => (
                    <FileItem key={file.filename} file={file} onFileDownloaded={fetchFileList} />
                  ))}
                </ul>
              )}
            </div>
          </div>
        );
      };

      // 主应用组件
      const App = () => {
        const handleFileUploaded = () => {
          // 刷新文件列表组件
          // FileList 组件会通过其内部的 useEffect 自动刷新
        };

        return (
          <div>
            <h1>文件上传下载服务</h1>
            <FileUpload onFileUploaded={handleFileUploaded} />
            <FileList />
          </div>
        );
      };

      // 渲染应用
      const root = ReactDOM.createRoot(document.getElementById('root'));
      root.render(<App />);
    </script>
  </body>
</html>
